package com.jchy.renderit.core;
 
public class Matrix
{
    protected double mat[][]={{1.0, 0.0, 0.0, 0.0},
                              {0.0, 1.0, 0.0, 0.0},
                              {0.0, 0.0, 1.0, 0.0},
                              {0.0, 0.0, 0.0, 1.0}};
    protected double invmat[][]={{1.0, 0.0, 0.0, 0.0},
                                 {0.0, 1.0, 0.0, 0.0},
                                 {0.0, 0.0, 1.0, 0.0},
                                 {0.0, 0.0, 0.0, 1.0}};

    protected double determinant;
    
    public Matrix()
    {
    }

    // Rotate matrix about the x axis at 0,0,0
    public void RotateX(double deciangle)
    {
        double radians=deciangle*Math.PI/180;
        double sinrad=Math.sin(radians);
        double cosrad=Math.cos(radians);
        int i,j;
        double copymat[][]=new double[4][4];
        for(i=0;i<4;i++)
            for(j=0;j<4;j++)
                copymat[i][j]=mat[i][j];
        // Column 1 (x) is not affected by rotate about x
        for(i=0;i<3;i++)
        {
            mat[i][1]=copymat[i][1]*cosrad+copymat[i][2]*sinrad;
            mat[i][2]=copymat[i][1]*sinrad-copymat[i][2]*cosrad;
        }
        // Rotate Column 3 around X
        mat[1][3]=copymat[1][3]*cosrad-copymat[2][3]*sinrad;
        mat[2][3]=copymat[1][3]*sinrad+copymat[2][3]*cosrad;
        Invert();     
                 
    }
    
    // Rotate matrix about the y axis at 0,0,0
    public void RotateY(double deciangle)
    {
        double radians=deciangle*Math.PI/180;
        double sinrad=Math.sin(radians);
        double cosrad=Math.cos(radians);
        int i,j;
        double copymat[][]=new double[4][4];
        for(i=0;i<4;i++)
            for(j=0;j<4;j++)
                copymat[i][j]=mat[i][j];
        // Column 1 (y) is not affected by rotate about y
        for(i=0;i<3;i++)
        {
            mat[i][0]=copymat[i][0]*cosrad+copymat[i][2]*sinrad;
            mat[i][2]=copymat[i][0]*sinrad-copymat[i][2]*cosrad;
        }
        // Rotate Column 3 around y
        mat[0][3]=copymat[0][3]*cosrad-copymat[2][3]*sinrad;
        mat[2][3]=copymat[0][3]*sinrad+copymat[2][3]*cosrad;
        Invert();     
                 
    }
    
    // Rotate matrix about the z axis at 0,0,0
    public void RotateZ(double deciangle)
    {
        double radians=deciangle*Math.PI/180;
        double sinrad=Math.sin(radians);
        double cosrad=Math.cos(radians);
        int i,j;
        double copymat[][]=new double[4][4];
        for(i=0;i<4;i++)
            for(j=0;j<4;j++)
                copymat[i][j]=mat[i][j];
        // Column 2 (z) is not affected by rotate about z
        for(i=0;i<3;i++)
        {
            mat[i][0]=copymat[i][0]*cosrad+copymat[i][1]*sinrad;
            mat[i][1]=copymat[i][0]*sinrad-copymat[i][1]*cosrad;
        }
        // Rotate Column 3 around z
        mat[0][3]=copymat[0][3]*cosrad-copymat[1][3]*sinrad;
        mat[1][3]=copymat[0][3]*sinrad+copymat[1][3]*cosrad;
        Invert();     
                 
    }
    
    public void translate(double x, double y, double z)
    {
        mat[0][3]+=x;  
        mat[1][3]+=y;  
        mat[2][3]+=z;  
        Invert();
    }

    public void setorigin(double x, double y, double z)
    {
        mat[0][3]=x;  
        mat[1][3]=y;  
        mat[2][3]=z;  
        Invert();
    }

    public void scale(double x, double y, double z)
    {
        mat[0][0]*=x; mat[0][1]*=y; mat[0][2]*=z;
        mat[1][0]*=x; mat[1][1]*=y; mat[1][2]*=z;
        mat[2][0]*=x; mat[2][1]*=y; mat[2][2]*=z;
        mat[0][3]*=x; mat[1][3]*=y; mat[2][3]*=z;
        Invert();
    }

    public void Orient(Dir u, Dir v, Dir n)
    {
        mat[0][0]=u.dx; mat[0][1]=v.dx; mat[0][2]=n.dx;
        mat[1][0]=u.dy; mat[1][1]=v.dy; mat[1][2]=n.dy;
        mat[2][0]=u.dz; mat[2][1]=v.dz; mat[2][2]=n.dz;
        Invert();
    }

    protected void SetDeterminant()
    {
        determinant = mat[0][0]*cofactor4(0,0)+
                      mat[0][1]*cofactor4(0,1)+
                      mat[0][2]*cofactor4(0,2);
    }
    

    protected void Invert()
    {
        SetDeterminant();
        if(determinant!=0) 
            for(int i=0; i<4; i++)
            {
                for(int j=0; j<4; j++)
                {
                    invmat[i][j]=cofactor4(j,i)/determinant;
                }
            }
        else System.out.println("Unable to invert: " + toString());
    }
    /*
    protected double cofactor3(int x, int y)
    {
        int[] first = {1, 2, 0};
        int[] second = {2, 0, 1};

        return mat[first[x]][first[y]]*mat[second[x]][second[y]] -
               mat[first[x]][second[y]]*mat[second[x]][first[y]];
    }    
    */
    private double cofactor4(int x, int y)
    {
        int[] first = {3, 0, 3, 0};
        int[] secnd = {2, 2, 1, 1};
        int[] third = {1, 3, 0, 2};

        return 
        mat[first[x]][first[y]]*(mat[secnd[x]][secnd[y]]*mat[third[x]][third[y]]-mat[secnd[x]][third[y]]*mat[third[x]][secnd[y]]) +
        mat[first[x]][secnd[y]]*(mat[secnd[x]][third[y]]*mat[third[x]][first[y]]-mat[secnd[x]][first[y]]*mat[third[x]][third[y]]) +
        mat[first[x]][third[y]]*(mat[secnd[x]][first[y]]*mat[third[x]][secnd[y]]-mat[secnd[x]][secnd[y]]*mat[third[x]][first[y]]);
               
    }    

    public Dir transform(Dir inDir)
    {
        Dir result = new Dir();
        result.dx = mat[0][0]*inDir.dx + mat[0][1]*inDir.dy + mat[0][2]*inDir.dz;
        result.dy = mat[1][0]*inDir.dx + mat[1][1]*inDir.dy + mat[1][2]*inDir.dz;
        result.dz = mat[2][0]*inDir.dx + mat[2][1]*inDir.dy + mat[2][2]*inDir.dz;
        return result;
    }
    
    public Point transform(Point inPoint)
    {
        Point result = new Point();
        result.x = mat[0][0]*inPoint.x + mat[0][1]*inPoint.y + mat[0][2]*inPoint.z + mat[0][3];
        result.y = mat[1][0]*inPoint.x + mat[1][1]*inPoint.y + mat[1][2]*inPoint.z + mat[1][3];
        result.z = mat[2][0]*inPoint.x + mat[2][1]*inPoint.y + mat[2][2]*inPoint.z + mat[2][3];
        return result;
    }

    public Dir untransform(Dir inDir)
    {
        Dir result = new Dir();
        result.dx = invmat[0][0]*inDir.dx + invmat[0][1]*inDir.dy + invmat[0][2]*inDir.dz;
        result.dy = invmat[1][0]*inDir.dx + invmat[1][1]*inDir.dy + invmat[1][2]*inDir.dz;
        result.dz = invmat[2][0]*inDir.dx + invmat[2][1]*inDir.dy + invmat[2][2]*inDir.dz;
        return result;
    }
    
    public Point untransform(Point inPoint)
    {
        Point result = new Point();
        result.x = invmat[0][0]*inPoint.x + invmat[0][1]*inPoint.y + invmat[0][2]*inPoint.z + invmat[0][3];
        result.y = invmat[1][0]*inPoint.x + invmat[1][1]*inPoint.y + invmat[1][2]*inPoint.z + invmat[1][3];
        result.z = invmat[2][0]*inPoint.x + invmat[2][1]*inPoint.y + invmat[2][2]*inPoint.z + invmat[2][3];
        return result;
    }

    public Point getTransTo()
    {
        return new Point(mat[0][3],mat[1][3],mat[2][3]);
    }

    public String toString()
    {   
        return ("Det: " + determinant + " Matrix:\n" + 
                mat[0][0] + " " + mat[0][1] + " " + mat[0][2] + " " + mat[0][3] + "\n" +
                mat[1][0] + " " + mat[1][1] + " " + mat[1][2] + " " + mat[1][3] + "\n" + 
                mat[2][0] + " " + mat[2][1] + " " + mat[2][2] + " " + mat[2][3] + "\n" + 
                mat[3][0] + " " + mat[3][1] + " " + mat[3][2] + " " + mat[3][3] + "\n" + 
                "Inverse Matrix:\n" +
                invmat[0][0] + " " + invmat[0][1] + " " + invmat[0][2] + " " + invmat[0][3] + "\n" + 
                invmat[1][0] + " " + invmat[1][1] + " " + invmat[1][2] + " " + invmat[1][3] + "\n" + 
                invmat[2][0] + " " + invmat[2][1] + " " + invmat[2][2] + " " + invmat[2][3] + "\n" +
                invmat[3][0] + " " + invmat[3][1] + " " + invmat[3][2] + " " + invmat[3][3]);
              
    }
    
}

